package com.start.common.mybaitsplus;

import com.start.common.exceptions.StartBusException;
import com.start.common.interfaces.IStartSQLFunFind;
import com.start.common.sql.IStartSQLFuncConvert;
import com.start.common.sql.StartSQLMappingManage;
import com.start.common.tools.StartSQLTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.*;

@Slf4j
@Configuration
@ConditionalOnProperty(prefix = "start", name = "db-type")
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class StartSQLQueryMapperInterceptor implements Interceptor {

    @Value("${start.db-type}")
    private String sqlDBType;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (StringUtils.isAllBlank(sqlDBType)) {
            return invocation.proceed();
        }

        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 通过MetaObject优雅访问对象的属性，这里是访问statementHandler的属性;：MetaObject是Mybatis提供的一个用于方便、
        // 优雅访问对象属性的对象，通过它可以简化代码、不需要try/catch各种reflect异常，同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        //实际执行的sql是经过层层封装，无法利用简单的一层反射获取到需要使用提供的快捷方法或者对获取到关键数据进行拼装
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
                new DefaultReflectorFactory());

        // 先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，然后就到BaseStatementHandler的成员变量mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        if (sqlCommandType != SqlCommandType.SELECT) {
            return invocation.proceed();
        }
        Connection connection = (Connection) invocation.getArgs()[0];
        String databaseProductName = connection.getMetaData().getDatabaseProductName();

        BoundSql boundSql = statementHandler.getBoundSql();
        // 获取到原始sql语句
        String sql = boundSql.getSql();
        log.debug("增强前SQL：{}", sql);
        sql = getSNewSql(databaseProductName, sql);
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql, sql);
        log.debug("增强后SQL：{}", sql);
        return invocation.proceed();
    }

    private String getSNewSql(String tDBType, String sql) {
        IStartSQLFuncConvert funConvert = StartSQLMappingManage.getInstance().getFunConvert(tDBType);
        if (funConvert == null || funConvert.getHandlerFun() == null || funConvert.getHandlerFun().isEmpty()) {
            return sql;
        }

        final String[] newSql = {sql};
        Map<String, String> handlerFun = funConvert.getHandlerFun();
        sqlFindFun(sql, (srcSql, funName, funAgr) -> {
            if (!handlerFun.containsKey(funName)) {
                return;
            }
            String newFunName = handlerFun.get(funName);
            String newAre = funConvert.handlerFunAgr(funName, funAgr, srcSql);
            String newFunSql = newFunName + "(" + newAre + ")";
            newSql[0] = newSql[0].replace(srcSql, newFunSql);
        });
        return newSql[0];
    }

//    public static void main(String[] args) {
//        String sql = "a.c(d),da,sum(a+1) d concat(a,d,if(a,null,d,a()))";
//        System.out.println("sql:" + sql.length());
//        sqlFindFun(sql, new IStartSQLFunFind() {
//
//            @Override
//            public void find(String srcSql, String funName, String funAgr) {
//                System.out.println(srcSql);
//            }
//        });
//
//    }


    private static void sqlFindFun(String sql, IStartSQLFunFind find) {
        if (find == null) {
            throw new StartBusException("parameter is not null");
        }
        List<Integer> indexes = new ArrayList<>();
        int firstIndex = -1;
        for (int i = 0; i < sql.length(); i++) {
            char c = sql.charAt(i);
            if (!StartSQLTools.charIsFunFlag(c)) {
                firstIndex = -1;
                continue;
            }
            if (StartSQLTools.charIsFunNameFlag(c)) {
                if (firstIndex < 1) {
                    firstIndex = i;
                }
                continue;
            }
            if (c != '(' && c != ')') {
                continue;
            }
            if (c == '(') {
                indexes.add(firstIndex);
                firstIndex = -1;
                continue;
            }
            int startIndex = indexes.get(indexes.size() - 1);
            String item = sql.substring(startIndex, i + 1);
            String funName = item.substring(0, item.indexOf("("));
            String funAgr = item.substring(item.indexOf("("), item.lastIndexOf("(") + 1);
            find.find(item, funName, funAgr);
            indexes.remove(indexes.size() - 1);
        }


    }


}
